﻿using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Hont.C2UTool
{
    public static class C2UUtility
    {
        #region Clipboard Win32 Interface
        [DllImport("user32.dll")]
        public static extern bool EmptyClipboard();
        [DllImport("user32.dll", SetLastError = true)]
        private extern static IntPtr SetClipboardData(uint format, IntPtr handle);
        [DllImport("user32.dll")]
        static extern IntPtr GetClipboardData(uint uFormat);
        [DllImport("user32.dll")]
        static extern bool IsClipboardFormatAvailable(uint format);
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool OpenClipboard(IntPtr hWndNewOwner);
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool CloseClipboard();
        [DllImport("kernel32.dll")]
        static extern IntPtr GlobalLock(IntPtr hMem);
        [DllImport("kernel32.dll")]
        static extern bool GlobalUnlock(IntPtr hMem);

        public const uint CF_UNICODETEXT = 13;
        #endregion

        public struct AnimationInfoPair
        {
            public AnimationCurve AnimationCurve;
            public EditorCurveBinding CurveBinding;
        }

        public class AnimationClipOpCache
        {
            public Func<AnimationInfoPair[], AnimationInfoPair[]> Op;
        }

        static Dictionary<int, UnityEngine.Object> mGUIObjFieldDict;
        static Dictionary<AnimationClip, List<AnimationClipOpCache>> mAnimationOpCacheDict;


        static C2UUtility()
        {
            mGUIObjFieldDict = new Dictionary<int, UnityEngine.Object>();
            mAnimationOpCacheDict = new Dictionary<AnimationClip, List<AnimationClipOpCache>>();
        }

        #region ---Animation Clip---

        public static void ChangeAnimationClipCurvePath(AnimationClip clip, Func<string, string> onChange)
        {
            ChangeAnimationClipCurve(clip, (arr) =>
            {
                for (int i = 0; i < arr.Length; i++)
                {
                    var item = arr[i];

                    var curveBinding = item.CurveBinding;

                    curveBinding.path = onChange(curveBinding.path);

                    item.CurveBinding = curveBinding;

                    arr[i] = item;
                }

                return arr;
            });
        }

        public static void ChangeAnimationClipCurve(AnimationClip clip, Func<AnimationInfoPair[], AnimationInfoPair[]> onChange)
        {
            if (!mAnimationOpCacheDict.ContainsKey(clip))
                mAnimationOpCacheDict.Add(clip, new List<AnimationClipOpCache>());

            mAnimationOpCacheDict[clip].Add(new AnimationClipOpCache() { Op = onChange });
        }

        public static void ApplyChangedAnimationClip()
        {
            foreach (var cache in mAnimationOpCacheDict)
            {
                var editorCurveBindings = AnimationUtility.GetCurveBindings(cache.Key);

                var pairList = new List<AnimationInfoPair>();

                for (int i = 0; i < editorCurveBindings.Length; i++)
                {
                    var item = editorCurveBindings[i];

                    var curve = AnimationUtility.GetEditorCurve(cache.Key, item);

                    pairList.Add(new AnimationInfoPair() { AnimationCurve = curve, CurveBinding = item });
                }

                cache.Key.ClearCurves();

                var pairArr = pairList.ToArray();

                for (int i = 0; i < cache.Value.Count; i++)
                {
                    var item = cache.Value[i];

                    pairArr = item.Op(pairArr);
                }

                for (int i = 0; i < pairArr.Length; i++)
                {
                    var item = pairArr[i];

                    AnimationUtility.SetEditorCurve(cache.Key, item.CurveBinding, item.AnimationCurve);
                }
            }

            mAnimationOpCacheDict.Clear();

            AssetDatabase.SaveAssets();
        }

        public static void ClearChangedAnimationClipCache()
        {
            mAnimationOpCacheDict.Clear();
        }

        #endregion

        #region ---win32---
        /// <summary>
        /// Invoke Process Execute MS-DOS Command
        /// </summary>
        public static void ProcessExecute(string command)
        {
            if (command != null && !command.Equals(""))
            {
                var process = new Process();
                var startInfo = new ProcessStartInfo();
                startInfo.FileName = "cmd.exe";
                startInfo.Arguments = "/C " + command;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardInput = false;
                startInfo.RedirectStandardOutput = true;
                startInfo.CreateNoWindow = true;

                process.StartInfo = startInfo;
                try
                {
                    if (process.Start())
                    {
                        process.WaitForExit(100);
                    }
                }
                catch
                {
                    UnityEngine.Debug.Log("error!");
                }
                finally
                {
                    if (process != null)
                        process.Close();
                }
            }
        }

        public static bool CopyToClipboard(uint id, string content)
        {
            if (OpenClipboard(IntPtr.Zero))
            {
                EmptyClipboard();
                IntPtr hmem = Marshal.StringToHGlobalUni(content);
                var ptr = GlobalLock(hmem);
                GlobalUnlock(ptr);
                SetClipboardData(id, ptr);
                CloseClipboard();
                return true;
            }
            return false;
        }

        public static string GetFromClipboard(uint id)
        {
            if (!IsClipboardFormatAvailable(id)) return null;
            if (!OpenClipboard(IntPtr.Zero)) return null;

            string data = null;
            var hGlobal = GetClipboardData(id);
            if (hGlobal != IntPtr.Zero)
            {
                var lpwcstr = GlobalLock(hGlobal);
                if (lpwcstr != IntPtr.Zero)
                {
                    data = Marshal.PtrToStringAuto(lpwcstr);
                    GlobalUnlock(lpwcstr);
                }
            }
            CloseClipboard();

            return data;
        }

        #endregion

        #region ---C2U Security String---
        public static string SecurityStringDecode(string str)
        {
            if (string.IsNullOrEmpty(str)) return "";

            var result = FromBase64String(str, Encoding.UTF8);
            return result;
        }

        public static string ConvertSecurityString(string str)
        {
            if (string.IsNullOrEmpty(str)) return "";

            return ToBase64String(str, Encoding.UTF8);
        }

        public static string FromBase64String(string str, Encoding enc)
        {
            return enc.GetString(Convert.FromBase64String(str));
        }

        public static string ToBase64String(string str, Encoding enc)
        {
            return Convert.ToBase64String(enc.GetBytes(str));
        }

        #endregion

        #region ---GUI---
        public static bool ColorButton(object content, Color color)
        {
            bool result = false;
            var tmp = GUI.color;
            GUI.color = color;
            result = GUILayout.Button(content == null ? "" : content.ToString());
            GUI.color = tmp;

            return result;
        }

        public static T TmpObjectField<T>(int id, string prefixTxt, bool allowSceneObj = false)
                where T : UnityEngine.Object
        {
            T result = default(T);

            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField(prefixTxt);

            if (!mGUIObjFieldDict.ContainsKey(id))
                mGUIObjFieldDict.Add(id, null);

            mGUIObjFieldDict[id] = EditorGUILayout.ObjectField(mGUIObjFieldDict[id], typeof(T), allowSceneObj);
            result = (T)mGUIObjFieldDict[id];

            EditorGUILayout.EndHorizontal();

            return result;
        }

        #endregion

        #region ---String Functions---
        public static string CombinePath(string str, string newPath)
        {
            return Path.Combine(str, newPath).Replace(@"\", "/");
        }

        public static string RemoveStr(string str, string inxKeyStr)
        {
            var inx = str.IndexOf(inxKeyStr);
            if (inx == -1) return "";
            return str.Remove(inx, inxKeyStr.Length);
        }

        public static string RemoveStr(string str, int startInx, string inxKeyStr)
        {
            var inx = str.IndexOf(inxKeyStr, startInx);
            if (inx == -1) return "";
            inx += inxKeyStr.Length;
            return str.Remove(startInx, inx - startInx);
        }

        public static string LastRemoveStr(string str, string keword, bool containKeyword = false)
        {
            var index = str.LastIndexOf(keword);
            if (index == -1) return null;

            var finalLength = containKeyword ? str.Length - index - keword.Length : str.Length - index;
            return LastRemoveStr(str, finalLength);
        }

        public static string LastRemoveStr(string str, int length)
        {
            return str.Remove(str.Length - length, length);
        }

        public static string ToAbsolutePath(string relativePath)
        {
            return ToAbsolutePath(relativePath, Directory.GetCurrentDirectory());
        }

        public static string ToAbsolutePath(string relativePath, string currentDirectory)
        {
            string result = "";

            var oldCurrentDirecotory = System.IO.Directory.GetCurrentDirectory();
            Directory.SetCurrentDirectory(currentDirectory);
            result = System.IO.Path.GetFullPath(relativePath).Replace(@"\", "/");
            Directory.SetCurrentDirectory(oldCurrentDirecotory);

            return result;
        }

        public static string ToRelativePath(string absolutePath, string relativeTo, char slash = '\\')
        {
            //from - www.cnphp6.com

            string[] absoluteDirectories = absolutePath.Split(slash);
            string[] relativeDirectories = relativeTo.Split(slash);

            //Get the shortest of the two paths
            int length = absoluteDirectories.Length < relativeDirectories.Length ? absoluteDirectories.Length : relativeDirectories.Length;

            //Use to determine where in the loop we exited
            int lastCommonRoot = -1;
            int index;

            //Find common root
            for (index = 0; index < length; index++)
                if (absoluteDirectories[index] == relativeDirectories[index])
                    lastCommonRoot = index;
                else
                    break;

            //If we didn't find a common prefix then throw
            if (lastCommonRoot == -1)
                throw new ArgumentException("Paths do not have a common base");

            //Build up the relative path
            StringBuilder relativePath = new StringBuilder();

            //Add on the ..
            for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
                if (absoluteDirectories[index].Length > 0)
                    relativePath.Append(".." + slash);

            //Add on the folders
            for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
                relativePath.Append(relativeDirectories[index] + slash);
            relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);

            return relativePath.ToString();
        }

        /// <summary>
        /// Return path directory(d:/MyFile/a.txt -> d:/MyFile)
        /// </summary>
        public static string GetDirectory(string str)
        {
            return Path.GetDirectoryName(str).Replace(@"\", "/");
        }

        /// <summary>
        /// Return removed path extension(a.txt -> a)
        /// </summary>
        public static string RemoveExt(string str)
        {
            var dotInx = str.LastIndexOf('.');

            return dotInx == -1 ? str : str.Substring(0, dotInx);
        }

        /// <summary>
        /// Return path file name and contain extension(d:/a.txt -> a.txt)
        /// </summary>
        public static string GetFileName(string str)
        {
            return LastSubstring(str.Replace("\\", "/"), "/");
        }

        /// <summary>
        /// Return path file name(d:/a.txt -> a)
        /// </summary>
        public static string GetFileNameNoExt(string str)
        {
            return RemoveExt(GetFileName(str));
        }

        public static string Substring(string str, int startInx, string inxKeyStr)
        {
            return Substring(str, startInx, inxKeyStr, false);
        }

        /// <summary>
        /// not contain keyword
        /// </summary>
        public static string Substring(string str, int startInx, string inxKeyStr, bool containKeyStr)
        {
            var inx = str.IndexOf(inxKeyStr, startInx);
            if (inx == -1) return "";

            if (!containKeyStr) inx += inxKeyStr.Length;
            return str.Substring(startInx, inx - 1);
        }

        public static string Substring(string str, string beginKey, string endKey)
        {
            return Substring(str, beginKey, false, endKey, false);
        }

        public static string Substring(string str, string beginKey, bool containBeginKey, string endKey, bool containEndKey)
        {
            var beginInx = str.IndexOf(beginKey);
            var endInx = str.IndexOf(endKey);
            if (beginInx == -1) return "";
            if (endInx == -1) return "";
            if (endInx < beginInx) return "";

            if (!containBeginKey) beginInx += beginKey.Length;
            if (containEndKey) endInx += endKey.Length;

            return str.Substring(beginInx, endInx - beginInx);
        }

        public static string LastSubstring(string str, int length)
        {
            return str.Substring(str.Length - length, length);
        }

        /// <summary>
        /// Get last substring and not contain keyword.
        /// </summary>
        public static string LastSubstring(string str, string inxKeyStr)
        {
            return LastSubstring(str, str.Length, inxKeyStr);
        }

        public static string LastSubstring(string str, int startInx, string inxKeyStr)
        {
            return LastSubstring(str, startInx, inxKeyStr, false);
        }

        public static string LastSubstring(string str, string inxKeyStr, bool containKeyStr)
        {
            return LastSubstring(str, str.Length, inxKeyStr, containKeyStr);
        }

        public static string LastSubstring(string str, int startInx, string inxKeyStr, bool containKeyStr)
        {
            var inx = str.LastIndexOf(inxKeyStr, startInx);
            if (inx == -1) return "";
            if (startInx < inx) return "";

            if (!containKeyStr) inx += inxKeyStr.Length;
            return str.Substring(inx, startInx - inx);
        }

        public static string GetAssetsAbsolutePath(UnityEngine.Object obj)
        {
            var path = Path.Combine(Directory.GetCurrentDirectory(), AssetDatabase.GetAssetPath(obj));
            return path.Replace(@"\", "/");
        }

        public static string ReadAssetsFileToString(UnityEngine.Object obj)
        {
            var fullPath = GetAssetsAbsolutePath(obj);
            return File.ReadAllText(fullPath);
        }

        public static string GetFullTransformPath(Transform transform, Transform root = null)
        {
            if (root == transform) return "";

            var parentList = new List<string>();
            var tmpParent = transform.parent;

            while (tmpParent != null && tmpParent != root)
            {
                if (tmpParent != null)
                {
                    parentList.Add(tmpParent.name);
                }
                tmpParent = tmpParent.parent;
            }
            parentList.Reverse();

            return (string.Join("/", parentList.ToArray()) + "/" + transform.name).TrimStart('/');
        }

        public static string SubstringPair(string str, string beginKey, string endKey)
        {
            var beginKeyInx = str.IndexOf(beginKey);
            if (beginKeyInx == -1) return null;

            var closeList = new List<int>();
            var endKeywordInx = -1;
            var endKeywordInxArr = TotalIndexOf(str, endKey);
            closeList.Clear();

            for (int i = 0; i < endKeywordInxArr.Length; i++)
            {
                int[] inxArr = TotalLastIndexOf(str, beginKey, endKeywordInxArr[i]);
                var inx = inxArr.Except(closeList).First();
                if (inx == beginKeyInx)
                {
                    endKeywordInx = endKeywordInxArr[i];
                    break;
                }

                closeList.Add(inx);
            }

            var beginCutNum = beginKeyInx + beginKey.Length;
            var endCutNum = endKeywordInx - beginCutNum;
            str = str.Substring(beginCutNum, endCutNum);

            return str;
        }

        public static int[] TotalLastIndexOf(string str, string keyword)
        {
            return TotalLastIndexOf(str, keyword, str.Length, str.Length + 1);
        }

        public static int[] TotalLastIndexOf(string str, string keyword, int startIndex)
        {
            return TotalLastIndexOf(str, keyword, startIndex, startIndex + 1);
        }

        public static int[] TotalLastIndexOf(string str, string keyword, int startIndex, int count)
        {
            var result = new List<int>();
            int indexOfResult = str.LastIndexOf(keyword, startIndex, count);
            int targetInx = startIndex - (count - 1);

            if (indexOfResult > -1)
                result.Add(indexOfResult);

            while (indexOfResult != -1)
            {
                indexOfResult -= keyword.Length;
                if (indexOfResult - targetInx < 0) break;
                indexOfResult = str.LastIndexOf(keyword, indexOfResult, indexOfResult - targetInx + 1);

                if (indexOfResult > -1)
                    result.Add(indexOfResult);
            }
            return result.ToArray();
        }

        public static int[] TotalIndexOf(string str, string keyword)
        {
            return TotalIndexOf(str, keyword, 0, str.Length);
        }

        public static int[] TotalIndexOf(string str, string keyword, int startIndex)
        {
            return TotalIndexOf(str, keyword, startIndex, str.Length);
        }

        public static int[] TotalIndexOf(string str, string keyword, int startIndex, int count)
        {
            var result = new List<int>();
            int indexOfResult = str.IndexOf(keyword, startIndex, count);
            int targetInx = count - 1;

            if (indexOfResult > -1)
                result.Add(indexOfResult);

            while (indexOfResult != -1)
            {
                indexOfResult += keyword.Length;
                if (targetInx - indexOfResult < 0) break;
                indexOfResult = str.IndexOf(keyword, indexOfResult, targetInx - indexOfResult + 1);

                if (indexOfResult > -1)
                    result.Add(indexOfResult);
            }
            return result.ToArray();
        }

        public static string[][] SplitEx(string str, char lv1SplitChar, char lv2SplitChat)
        {
            var lv1SplitArr = str.Split(lv1SplitChar);
            var result = new string[lv1SplitArr.Length][];

            for (int i = 0; i < result.Length; i++)
            {
                var lv2SplitArr = lv1SplitArr[i].Split(lv2SplitChat);
                result[i] = new string[lv2SplitArr.Length];
                for (int j = 0; j < lv2SplitArr.Length; j++)
                {
                    result[i][j] = lv2SplitArr[j];
                }
            }

            return result;
        }

        #endregion

        #region ---Reflection---

        public static FieldInfo[] GetObjectPublicFields(object obj)
        {
            return obj
                .GetType()
                .GetFields(BindingFlags.Instance | BindingFlags.Public);
        }

        public static Type GetRuntimeType(string typeFullName)
        {
            if (typeFullName == null || typeFullName.Length == 0) return null;

            var result = Type.GetType(typeFullName);

            if (result == null)
            {
                result = Types.GetType(typeFullName, "Assembly-CSharp");
            }

            if (result == null)
            {
                var assembly = Assembly.Load("UnityEngine");
                result = assembly.GetType(typeFullName);
            }

            return result;
        }

        public static TInterface[] GetAssemblyInterfacesAndCreate<TInterface>(object assemblyReferenceObject)
        {
            return GetAssemblyInterfacesAndCreate<TInterface>(Assembly.GetAssembly(assemblyReferenceObject.GetType()));
        }

        public static TInterface[] GetAssemblyInterfacesAndCreate<TInterface>(Assembly assembly)
        {
            var types = GetAssemblyInterfaceTypes(assembly, typeof(TInterface));
            var result = new TInterface[types.Length];

            for (int i = 0; i < result.Length; i++)
            {
                if (types[i].IsAbstract) continue;

                result[i] = GetAssemblyCreateInstance<TInterface>(assembly, types[i]);
            }

            return result;
        }

        public static Type[] GetAssemblyInterfaceTypes(Assembly assembly, Type type)
        {
            var types = assembly.GetTypes()
                .Where(m => m.GetInterface(type.Name) != null)
                .ToArray();

            return types;
        }

        public static T GetAssemblyCreateInstance<T>(Assembly assembly, Type type)
        {
            return (T)assembly.CreateInstance(type.FullName);
        }

        #endregion

        #region ---Transform---

        public static IEnumerable<Transform> ChildToEnumerableT(Transform tfm, bool hasSelf = false)
        {
            var result = new List<Transform>();

            ChildForEach(tfm, m => result.Add(m), hasSelf);

            return result;
        }

        public static void ChildForEach(Transform tfm, Action<Transform> action, bool hasSelf = false)
        {
            if (hasSelf) action(tfm);
            RecursiveChildTransform(tfm, action);
        }

        public static T FindAllChild<T>(Transform tfm, bool hasSelf = true)
        where T : Component
        {
            T result = default(T);

            if (hasSelf)
            {
                if (tfm.GetComponent<T>() != null)
                {
                    return tfm.GetComponent<T>();
                }
            }

            ChildForEach(tfm, m =>
            {
                if (m.GetComponent<T>() != null) result = m.GetComponent<T>();
            });

            return result;
        }

        public static GameObject FindAllChild(Transform transform, string name)
        {
            var findResult = FindAllChild<Transform>(transform, name);

            if (findResult == null)
            {
                throw new NullReferenceException("Not Found Object: " + name);
            }

            return findResult.gameObject;
        }

        public static T FindAllChild<T>(Transform transform, string name, bool hasSelf = true)
        where T : Component
        {
            T result = default(T);

            if (hasSelf)
            {
                if (transform.name == name && transform.GetComponent<T>() != null)
                {
                    return transform.GetComponent<T>();
                }
            }

            C2UUtility.ChildForEach(transform, m =>
            {
                if (m.name == name && m.GetComponent<T>() != null) result = m.GetComponent<T>();
            });

            return result;
        }

        static void RecursiveChildTransform(Transform transform, Action<Transform> action)
        {
            foreach (Transform item in transform)
            {
                if (action != null) action(item);

                if (item.childCount > 0)
                {
                    RecursiveChildTransform(item, action);
                }
            }
        }

        #endregion

        #region ---Collection---

        public static IEnumerable<TOut> ConvertAll<TIn, TOut>(IEnumerable<TIn> source, Func<TIn, TOut> convert)
        {
            var result = new List<TOut>();
            var handle = source.GetEnumerator();

            while (handle.MoveNext())
            {
                result.Add(convert(handle.Current));
            }

            return result;
        }

        #endregion
    }
}
